/**
 * Created by PanJiaChen on 16/11/18.
 */

import moment from 'moment'
import { getDict } from '@/api/system/dict'

/**
 * Parse the time to string
 * @param {(Object|string|number)} time
 * @param {string} cFormat
 * @returns {string | null}
 */
export function parseTime(time, cFormat = 'YYYY-MM-DD HH:mm:SS') {
  const res = moment(time).format(cFormat)
  if (res === 'Invalid date') {
    return ''
  } else {
    return res
  }
}

/**
 * @param {number} time
 * @param {string} option
 * @returns {string}
 */
export function formatTime(time, option) {
  if (('' + time).length === 10) {
    time = parseInt(time) * 1000
  } else {
    time = +time
  }
  const d = new Date(time)
  const now = Date.now()

  const diff = (now - d) / 1000

  if (diff < 30) {
    return '刚刚'
  } else if (diff < 3600) {
    // less 1 hour
    return Math.ceil(diff / 60) + '分钟前'
  } else if (diff < 3600 * 24) {
    return Math.ceil(diff / 3600) + '小时前'
  } else if (diff < 3600 * 24 * 2) {
    return '1天前'
  }
  if (option) {
    return parseTime(time, option)
  } else {
    return (
      d.getMonth() +
      1 +
      '月' +
      d.getDate() +
      '日' +
      d.getHours() +
      '时' +
      d.getMinutes() +
      '分'
    )
  }
}

/**
 * @param {string} url
 * @returns {Object}
 */
export function getQueryObject(url) {
  url = url == null ? window.location.href : url
  const search = url.substring(url.lastIndexOf('?') + 1)
  const obj = {}
  const reg = /([^?&=]+)=([^?&=]*)/g
  search.replace(reg, (rs, $1, $2) => {
    const name = decodeURIComponent($1)
    let val = decodeURIComponent($2)
    val = String(val)
    obj[name] = val
    return rs
  })
  return obj
}

/**
 * @param {string} input value
 * @returns {number} output value
 */
export function byteLength(str) {
  // returns the byte length of an utf8 string
  let s = str.length
  for (var i = str.length - 1; i >= 0; i--) {
    const code = str.charCodeAt(i)
    if (code > 0x7f && code <= 0x7ff) s++
    else if (code > 0x7ff && code <= 0xffff) s += 2
    if (code >= 0xDC00 && code <= 0xDFFF) i--
  }
  return s
}

/**
 * @param {Array} actual
 * @returns {Array}
 */
export function cleanArray(actual) {
  const newArray = []
  for (let i = 0; i < actual.length; i++) {
    if (actual[i]) {
      newArray.push(actual[i])
    }
  }
  return newArray
}

/**
 * @param {Object} json
 * @returns {Array}
 */
export function param(json) {
  if (!json) return ''
  return cleanArray(
    Object.keys(json).map(key => {
      if (json[key] === undefined) return ''
      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
    })
  ).join('&')
}

/**
 * @param {string} url
 * @returns {Object}
 */
export function param2Obj(url) {
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
  if (!search) {
    return {}
  }
  const obj = {}
  const searchArr = search.split('&')
  searchArr.forEach(v => {
    const index = v.indexOf('=')
    if (index !== -1) {
      const name = v.substring(0, index)
      const val = v.substring(index + 1, v.length)
      obj[name] = val
    }
  })
  return obj
}

/**
 * @param {string} val
 * @returns {string}
 */
export function html2Text(val) {
  const div = document.createElement('div')
  div.innerHTML = val
  return div.textContent || div.innerText
}

/**
 * Merges two objects, giving the last one precedence
 * @param {Object} target
 * @param {(Object|Array)} source
 * @returns {Object}
 */
export function objectMerge(target, source) {
  if (typeof target !== 'object') {
    target = {}
  }
  if (Array.isArray(source)) {
    return source.slice()
  }
  Object.keys(source).forEach(property => {
    const sourceProperty = source[property]
    if (typeof sourceProperty === 'object') {
      target[property] = objectMerge(target[property], sourceProperty)
    } else {
      target[property] = sourceProperty
    }
  })
  return target
}

/**
 * @param {HTMLElement} element
 * @param {string} className
 */
export function toggleClass(element, className) {
  if (!element || !className) {
    return
  }
  let classString = element.className
  const nameIndex = classString.indexOf(className)
  if (nameIndex === -1) {
    classString += '' + className
  } else {
    classString =
      classString.substr(0, nameIndex) +
      classString.substr(nameIndex + className.length)
  }
  element.className = classString
}

/**
 * @param {string} type
 * @returns {Date}
 */
export function getTime(type) {
  if (type === 'start') {
    return new Date().getTime() - 3600 * 1000 * 24 * 90
  } else {
    return new Date(new Date().toDateString())
  }
}

/**
 * This is just a simple version of deep copy
 * Has a lot of edge cases bug
 * If you want to use a perfect deep copy, use lodash's _.cloneDeep
 * @param {Object} source
 * @returns {Object}
 */
export function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone')
  }
  const targetObj = source.constructor === Array ? [] : {}
  Object.keys(source).forEach(keys => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys])
    } else {
      targetObj[keys] = source[keys]
    }
  })
  return targetObj
}

/**
 * @param {Array} arr
 * @returns {Array}
 */
export function uniqueArr(arr) {
  return Array.from(new Set(arr))
}

/**
 * @returns {string}
 */
export function createUniqueString() {
  const timestamp = +new Date() + ''
  const randomNum = parseInt((1 + Math.random()) * 65536) + ''
  return (+(randomNum + timestamp)).toString(32)
}

/**
 * Check if an element has a class
 * @param {HTMLElement} elm
 * @param {string} cls
 * @returns {boolean}
 */
export function hasClass(ele, cls) {
  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}

/**
 * Add class to element
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function addClass(ele, cls) {
  if (!hasClass(ele, cls)) ele.className += ' ' + cls
}

/**
 * Remove class from element
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function removeClass(ele, cls) {
  if (hasClass(ele, cls)) {
    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
    ele.className = ele.className.replace(reg, ' ')
  }
}

/**
 * 判断数据是否为Object对象
 * @param object 检测对象
 * @returns {boolean} isObject
 */
export function isObj(object) {
  return object && typeof (object) === 'object' && Object.prototype.toString.call(object).toLowerCase() === '[object object]'
}

/**
 * 判断字符串是否为JSON字符串
 * @param str 待检测字符串
 * @returns {boolean} isJson
 */
export function isJSONStr(str) {
  if (typeof str === 'string') {
    try {
      var obj = JSON.parse(str)
      if (typeof obj === 'object' && obj) {
        return true
      } else {
        return false
      }
    } catch (e) {
      console.log('error：' + str + '!!!' + e)
      return false
    }
  }
}

/**
 * 为对象添加新的别名
 * @param obj 原有对象
 * @param filedObj
 * 新字段数据对象
 * {
 *    oldKey: newKey
 * }
 * @returns {*}
 */
export function obj2newField(obj, filedObj) {
  const newObj = { ...obj }
  if (isObj(filedObj)) {
    Object.keys(filedObj).forEach((key) => {
      if (newObj[key] !== undefined) {
        newObj[filedObj[key]] = newObj[key]
      }
    })
  }

  return newObj
}

/**
 * 获取树中所有子节点
 */
export function getTreeBaseData(tree) {
  const subTree = [] // test存放所有子节点的数组
  // 第一步骤：子节点放在一个数组中
  const requestList = (data) => {
    data && data.map(item => {
      if (item.children && item.children.length > 0) {
        requestList(item.children)
      } else {
        subTree.push(item.id)
      }
      return null
    })
    return subTree
  }

  return requestList(tree)
}

/**
 * 获取数组中的交集
 * @param getArrIntersection
 * @param Arr
 * @returns {*[]}
 */
export function getArrIntersection(uniqueArr, Arr) {
  const uniqueChild = []
  for (var i in Arr) {
    for (var k in uniqueArr) {
      if (uniqueArr[k] === Arr[i]) {
        uniqueChild.push(uniqueArr[k])
      }
    }
  }
  return uniqueChild
}

/**
 * 数据转换为树形（递归），示例：toTreeByRecursion(source, 'id', 'parentId', null, 'children')
 * @param {Array} source 数据
 * @param {String} idField 标识字段名称
 * @param {String} parentIdField 父标识字段名称
 * @param {Any} parentIdNoneValue 父级标识空值
 * @param {String} childrenField 子节点字段名称
 * @param {Object} treeOption tree树形配置
 */
export function toTreeByRecursion(
  source = [],
  idField = 'id',
  parentIdField = 'parentId',
  parentIdNoneValue = '',
  childrenField = 'children',
  treeOption = undefined
) {
  const treeOptions = {
    enable: false, // 是否开启转tree插件数据
    keyField: 'key', // 标识字段名称，默认为key
    valueField: 'value', // 值字段名称，默认为value
    titleField: 'title', // 标题字段名称，默认为title

    keyFieldBind: 'id', // 标识字段绑定字段名称，默认为id
    valueFieldBind: 'id', // 值字段名称绑定字段名称，默认为id
    titleFieldBind: 'name' // 标题字段名称绑定字段名称，默认为name
  }
  // 合并tree树形配置
  if (treeOption) {
    Object.assign(treeOptions, treeOption)
  }

  // 对源数据深度克隆
  const cloneData = JSON.parse(JSON.stringify(source))
  return cloneData.filter(parent => {
    // 返回每一项的子级数组
    const branchArr = cloneData.filter(child => parent[idField] === child[parentIdField])

    // 绑定tree树形配置
    if (treeOptions.enable) {
      branchArr.map(child => {
        child[treeOptions.keyField] = child[treeOptions.keyFieldBind]
        child[treeOptions.valueField] = child[treeOptions.valueFieldBind]
        child[treeOptions.titleField] = child[treeOptions.titleFieldBind]
        return child
      })
    }

    // 如果存在子级，则给父级添加一个children属性，并赋值，否则赋值为空数组
    if (branchArr.length > 0) {
      parent[childrenField] = branchArr
    }

    // 绑定tree树形配置
    if (treeOptions.enable) {
      parent[treeOptions.keyField] = parent[treeOptions.keyFieldBind]
      parent[treeOptions.valueField] = parent[treeOptions.valueFieldBind]
      parent[treeOptions.titleField] = parent[treeOptions.titleFieldBind]
    }

    return parent[parentIdField] === parentIdNoneValue // 返回第一层
  })
}

/**
 * 将数组转变为tree结构与Map结构
 * @param data 原数组
 * @param idFiled 标识字段名称
 * @param parentIdField 父标识字段名称
 * @returns {{map: {}, treeData: []}}
 */
export function toTreeAndMap(data, idFiled = '_id', parentIdField = 'parentId') { // 递归树算法 将pid-id的数据库返回的json 转为树json
  const deepData = JSON.parse(JSON.stringify(data))

  // 删除 所有 children,以防止多次调用
  deepData.forEach(function(item) {
    delete item.children
  })

  // 将数据存储为 以id为KEY的map索引数据列
  const map = {}
  deepData.forEach(function(item) {
    map[item[idFiled]] = item
  })
  // console.log(map);

  const val = []
  deepData.forEach(function(item) {
    // 以当前遍历项，的pid,去map对象中找到索引的id
    var parent = map[item[parentIdField]]
    // 好绕啊，如果找到索引，那么说明此项不在顶级当中,那么需要把此项添加到，他对应的父级中
    if (parent) {
      (parent.children || (parent.children = [])).push(item) // 这里更改的是map对象的数据(索引数据)
      // console.log(map);
    } else {
      // 如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中，作为顶级
      val.push(item)
    }
  })

  return {
    treeData: val, // 树结构json数据 可以渲染html
    map // 索引数据 方便通过ID查找所有子节点ID
  }
}

/**
 * 删除树形结构中所有的空children
 * @param tree
 * @returns {*}
 */
export function deleteNullChildren(tree) {
  const deepData = JSON.parse(JSON.stringify(tree))

  return deepData.filter((item) => {
    if (Array.isArray(item.children) && item.children.length === 0) {
      delete item.children
    }
    if (Array.isArray(item.children) && item.children.length > 0) {
      item.children = deleteNullChildren(item.children)
    }

    return true
  })
}

/**
 *  根据id查询当前树节点
 * @param tree 树
 * @param findId 查询id
 * @returns {*}
 */
export function findIdOnTree(tree, findId) {
  for (var i = 0; i < tree.length; i++) {
    if (tree[i].id === findId) {
      return tree[i].children
    }
    if (Array.isArray(tree[i].children)) {
      var findResult = findIdOnTree(tree[i].children, findId)
      if (findResult) {
        return findResult
      }
    }
  }
}

export function tree2Array(treeObj, rootid) {
  const temp = [] // 设置临时数组，用来存放队列
  const out = [] // 设置输出数组，用来存放要输出的一维数组
  temp.push(treeObj)
  // 首先把根元素存放入out中
  let parentId = rootid
  const obj = deepClone(treeObj)
  obj.parentId = parentId
  delete obj['children']
  out.push(obj)
  // 对树对象进行广度优先的遍历
  while (temp.length > 0) {
    const first = temp.shift()
    const children = first.children
    if (children && children.length > 0) {
      parentId = first.parentId
      const len = first.children.length
      for (let i = 0; i < len; i++) {
        temp.push(children[i])
        const obj = deepClone(children[i])
        obj.parentId = parentId
        delete obj['children']
        out.push(obj)
      }
    }
  }
  return out
}

/**
 * 获取字典数据，并添加到vue实例
 */
export function getCodeDictMap(vue, params = {}, callback) {
  const type = Object.keys(params).join(',')
  getDict(type).then((res) => {
    if (res && res.code === 'A00000') {
      const map = {}
      res.data.forEach((dict) => {
        if (!map[dict.dictType]) {
          map[dict.dictType] = []
        }
        map[dict.dictType].push(dict)
      })

      for (const item in params) {
        vue[params[item]] = map[item]
      }

      if (callback && typeof callback === 'function') {
        callback()
      }
    }
  })
}

/**
 * 清楚参数为空字符传的参数
 * @param vue
 * @param params
 */
export function parseQueryParams(params = {}) {
  const dict = JSON.parse(JSON.stringify(params))
  for (const item in dict) {
    if (dict[item] === '' || dict[item] === undefined) {
      dict[item] = undefined
    }
  }

  return dict
}

// 节流函数
// 思路： 第一次先设定一个变量true，
// 第二次执行这个函数时，会判断变量是否true，
// 是则返回。当第一次的定时器执行完函数最后会设定变量为flase。
// 那么下次判断变量时则为flase，函数会依次运行。
export function throttle(fn, delay = 200) {
  // 首先设定一个变量，在没有执行我们的定时器时为null
  var timer = null
  return function() {
    // 当我们发现这个定时器存在时，则表示定时器已经在运行中，需要返回
    if (timer) return
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}

// 防抖函数
// 首次运行时把定时器赋值给一个变量， 第二次执行时，
// 如果间隔没超过定时器设定的时间则会清除掉定时器，
// 重新设定定时器， 依次反复， 当我们停止下来时，
// 没有执行清除定时器， 超过一定时间后触发回调函数。
// 参考文档：https://segmentfault.com/q/1010000021145192
export function debounce(fn, delay = 200) {
  let timer
  return function() {
    const that = this
    const _args = arguments // 存一下传入的参数
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(function() {
      fn.apply(that, _args)
    }, delay)
  }
}

export function rebuildData(value, arr, searchKey = 'name', children = 'children') {
  if (!arr) {
    return []
  }
  const newarr = []
  arr.forEach(element => {
    if (element[searchKey].indexOf(value) > -1) {
      const ab = rebuildData(value, element[children])
      const obj = {
        ...element,
        [children]: ab
      }
      newarr.push(obj)
    } else {
      if (element[children] && element[children].length > 0) {
        const ab = rebuildData(value, element[children])
        const obj = {
          ...element,
          [children]: ab
        }
        if (ab && ab.length > 0) {
          newarr.push(obj)
        }
      }
    }
  })
  return newarr
}

/**
 * 文件导出方法
 * @param fileName 文件名
 * @param fileBlob 文件blob流 或者文件 url
 */
export function exportFile(fileName, fileBlob) {
  var a = document.createElement('a') // 创建一个<a></a>标签

  if (typeof fileBlob === 'string') {
    // window.open(fileBlob)
    a.href = fileBlob
  } else {
    if (fileBlob instanceof Blob) {
      a.href = URL.createObjectURL(fileBlob)
    } else {
      a.href = URL.createObjectURL(new Blob(fileBlob))
    }
  }

  a.download = fileName // 设置文件名
  a.target = '_blank'
  a.style.display = 'none' // 障眼法藏起来a标签
  document.body.appendChild(a) // 将a标签追加到文档对象中
  a.click() // 模拟点击了a标签，会触发a标签的href的读取，浏览器就会自动下载了
  a.remove() // 一次性的，用完就删除a标签
}
